package org.track.web.view;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.validation.BindingResult;
import org.springframework.web.servlet.view.AbstractView;

import flexjson.JSONSerializer;

/**
 * TODO [Agregar documentacion de la clase]
 * @author Jorge A. Rodr�guez Campos (jorge.rodriguez@ife.org.mx, jorgerdc79@hotmail.com)
 * @version 1.0
 */
public class MappingFlexJsonView extends AbstractView {

	private String[] modelNamesToRender;

	private Set<JsonEntry> entriesToRender;

	private String[] attributeBeanNamesToInclude;

	private String[] attributeBeanNamesToExclude;

	/**
	 * Logger para todas las instancias de la clase
	 */
	private static final Log logger = LogFactory.getLog(MappingFlexJsonView.class);

	@Override
	protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
		response.setContentType("application/json");
		response.setCharacterEncoding("UTF-8");
	}

	/*
	 * La documentaci�n de este m�todo se encuentra en la clase o interface que lo declara
	 * (non-Javadoc)
	 * @see
	 * org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel(java.
	 * util.Map, javax.servlet.http.HttpServletRequest,
	 * javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void renderMergedOutputModel(Map<String, Object> model,
			HttpServletRequest request, HttpServletResponse response) throws Exception {

		String responseToRender;
		if (modelNamesToRender != null) {
			responseToRender = serializeModelAttributes(model);
		} else if (entriesToRender != null) {
			responseToRender = serialiceEntries();
		} else {
			responseToRender = serializeModel(model);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("respuesta JSON a enviar al cliente: " + responseToRender);
		}

		response.getOutputStream().write(responseToRender.getBytes());
	}

	/**
	 * TODO [Agregar documentacion al m�todo]
	 * @author Jorge A. Rodr�guez Campos (jorge.rodriguez@ife.org.mx,
	 *         jorgerdc79@hotmail.com)
	 * @param model
	 * @return
	 */
	private String serializeModel(Map<String, Object> model) {
		JSONSerializer serializer;
		serializer = new JSONSerializer();
		Map<String, Object> subMap;

		subMap = new HashMap<String, Object>(model.size());
		for (Map.Entry<String, Object> entry : model.entrySet()) {
			if (!(entry.getValue() instanceof BindingResult)) {
				subMap.put(entry.getKey(), entry.getValue());
			}
		}

		return serializer.exclude("*.class").serialize(subMap);
	}

	/**
	 * TODO [Agregar documentacion al m�todo]
	 * @author Jorge A. Rodr�guez Campos (jorge.rodriguez@ife.org.mx,
	 *         jorgerdc79@hotmail.com)
	 * @param entriesToRender2
	 * @return
	 */
	private String serialiceEntries() {

		JSONSerializer serializer;
		serializer = new JSONSerializer();
		return serializer.exclude("*.class").serialize(entriesToRender);

	}

	/**
	 * TODO [Agregar documentacion al m�todo]
	 * @author Jorge A. Rodr�guez Campos (jorge.rodriguez@ife.org.mx,
	 *         jorgerdc79@hotmail.com)
	 * @param model
	 * @return
	 */
	private String serializeModelAttributes(Map<String, Object> model) {

		JSONSerializer serializer;
		Map<String, Object> subMap = new HashMap<String, Object>(modelNamesToRender.length);
		String jsonString;

		for (String attributeName : modelNamesToRender) {
			subMap.put(attributeName, model.get(attributeName));
		}

		serializer = new JSONSerializer();

		if (attributeBeanNamesToInclude != null) {

			if (attributeBeanNamesToExclude == null) {
				attributeBeanNamesToExclude = new String[] { "*" };
			}

			jsonString = serializer.include(attributeBeanNamesToInclude).exclude(
					attributeBeanNamesToExclude).exclude("*.class").serialize(subMap);
		} else {

			if (attributeBeanNamesToExclude == null) {
				jsonString = serializer.exclude("*.class").serialize(subMap);
			} else {
				jsonString = serializer.exclude("*.class")
						.exclude(attributeBeanNamesToExclude).serialize(subMap);
			}

		}

		return jsonString;
	}

	/**
	 * TODO [Agregar documentaci�n del atributo]
	 */
	public MappingFlexJsonView() {
		// constructor por default
	}

	/**
	 * TODO [Agregar documentacion al m�todo]
	 * @author Jorge A. Rodr�guez Campos (jorge.rodriguez@ife.org.mx,
	 *         jorgerdc79@hotmail.com)
	 * @param attributeNames
	 */
	public MappingFlexJsonView(String... attributeNames) {

		this.modelNamesToRender = attributeNames;
	}

	/**
	 * TODO [Agregar documentaci�n del atributo]
	 * @param keyBeanAttributeName
	 * @param valueBeanAttributeName
	 * @param collectionOfBeans
	 */
	public MappingFlexJsonView(String keyBeanAttributeName, String valueBeanAttributeName,
			Collection< ?> collectionOfBeans) {

		Set<JsonEntry> entries = new HashSet<JsonEntry>(collectionOfBeans.size());
		JsonEntry entry;
		try {
			for (Object bean : collectionOfBeans) {

				entry = new JsonEntry();
				entry.setKey(invokeGetterMethod(keyBeanAttributeName, bean));
				entry.setValue(invokeGetterMethod(valueBeanAttributeName, bean));
				entries.add(entry);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		this.entriesToRender = entries;
	}

	/**
	 * TODO [Agregar documentaci�n del atributo]
	 * @param valueEnumAttributeName
	 * @param enumArray
	 */
	public MappingFlexJsonView(String valueEnumAttributeName, Enum< ? extends Enum>[] enumArray) {

		Set<JsonEntry> entries = new HashSet<JsonEntry>(enumArray.length);
		JsonEntry entry;

		try {
			for (Enum bean : enumArray) {
				entry = new JsonEntry();
				entry.setKey(bean.name());
				entry.setValue(invokeGetterMethod(valueEnumAttributeName, bean));
				entries.add(entry);
			}

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		this.entriesToRender = entries;

	}

	private Object invokeGetterMethod(String property, Object bean) throws Exception {
		Method method;
		StringBuffer sb;

		sb = new StringBuffer();
		sb.append("get");
		sb.append(property.substring(0, 1).toUpperCase());
		sb.append(property.substring(1));

		method = bean.getClass().getDeclaredMethod(sb.toString());
		return method.invoke(bean);

	}

	/**
	 * TODO [Agregar documentacion al m�todo]
	 * @author Jorge A. Rodr�guez Campos (jorge.rodriguez@ife.org.mx,
	 *         jorgerdc79@hotmail.com)
	 * @param attributeBeanNames
	 */
	public void setIncludes(String... attributeBeanNames) {

		this.attributeBeanNamesToInclude = attributeBeanNames;

	}

	/**
	 * TODO [Agregar documentacion al m�todo]
	 * @author Jorge A. Rodr�guez Campos (jorge.rodriguez@ife.org.mx,
	 *         jorgerdc79@hotmail.com)
	 * @param attributeBeanNames
	 */
	public void setExcludes(String... attributeBeanNames) {

		this.attributeBeanNamesToExclude = attributeBeanNames;

	}
}
